using System;
using System.Data;
using System.Diagnostics;
using System.Transactions;
using System.Collections.Generic;

namespace Adoor.Data
{
    public class TransactionalDataSetLite : IEnlistmentNotification
    {
          
        #region data members
        DataSet m_Source;
        DataSet m_Clone;
        Transaction m_CurrentTransaction;
        Dictionary<DataTable, Dictionary<DataRow, object>> m_ChangedRows = new Dictionary<DataTable, Dictionary<DataRow, object>>();
        Dictionary<DataTable, object> m_ClearedTables = new Dictionary<DataTable, object>();
        #endregion

        #region constructors
        public TransactionalDataSetLite(DataSet value)
        {
            m_Source = value;
        }
        #endregion

        #region dataset events
        public void DataColumnChangeEventHandler(Object sender, DataColumnChangeEventArgs e)
        {
            if (null == m_CurrentTransaction) 
                return;
            if (m_ClearedTables.ContainsKey(e.Row.Table)) 
                return;

            if (e.ProposedValue == e.Row[e.Column]) 
                return;

            bool constainsKey = m_ChangedRows.ContainsKey(e.Row.Table);
            if (constainsKey && m_ChangedRows[e.Row.Table].ContainsKey(e.Row)) 
                return;

            m_Clone.Tables[e.Row.Table.TableName].ImportRow(e.Row);
            if (!constainsKey) 
                m_ChangedRows.Add(e.Row.Table, new Dictionary<DataRow, object>());
            m_ChangedRows[e.Row.Table].Add(e.Row, null);
        }

        public void DataRowChangeEventHandler(Object sender, DataRowChangeEventArgs e)
        {
            if (null == m_CurrentTransaction) 
                return;
            if (m_ClearedTables.ContainsKey(e.Row.Table)) 
                return;

            if (e.Action == DataRowAction.Nothing) 
                return;

            bool constainsKey = m_ChangedRows.ContainsKey(e.Row.Table);

            if (constainsKey && m_ChangedRows[e.Row.Table].ContainsKey(e.Row)) 
                return;

            if (e.Action != DataRowAction.Add) 
                m_Clone.Tables[e.Row.Table.TableName].ImportRow(e.Row);

            if (!constainsKey) 
                m_ChangedRows.Add(e.Row.Table, new Dictionary<DataRow, object>());
            
            m_ChangedRows[e.Row.Table].Add(e.Row, null);
        }

        public void DataTableClearEventHandler(Object sender, DataTableClearEventArgs e)
        {
            if (null == m_CurrentTransaction) 
                return;
            if (m_ClearedTables.ContainsKey(e.Table)) 
                return;

            Rollback(e.Table);
            m_Clone.Tables[e.TableName].Clear();
            m_Clone.Tables[e.TableName].Merge(e.Table);
            m_ClearedTables.Add(e.Table, null);
        }
        #endregion

        #region IEnlistmentNotification implementation
        void IEnlistmentNotification.Commit(Enlistment enlistment)
        {
            Delist();
            //m_Source.AcceptChanges(); 
            enlistment.Done();
        }

        void IEnlistmentNotification.InDoubt(Enlistment enlistment)
        {
            enlistment.Done();
        }

        void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
        {
            preparingEnlistment.Prepared();
        }

        void IEnlistmentNotification.Rollback(Enlistment enlistment)
        {
            bool constraintsAreEnabled = m_Source.EnforceConstraints;
            m_Source.EnforceConstraints = false;
            foreach (DataTable dt in m_ChangedRows.Keys)
            {
                foreach (DataRow dr in m_ChangedRows[dt].Keys)
                {
                    if (dr.RowState != DataRowState.Detached) 
                        dt.Rows.Remove(dr);
                }
            }
            foreach (DataTable clonedt in m_Clone.Tables)
            {
                DataTable dt = m_Source.Tables[clonedt.TableName];
                foreach (DataRow dr in clonedt.Rows)
                {
                    dt.ImportRow(dr);
                }
            }
            m_Source.EnforceConstraints = constraintsAreEnabled;

            Delist();
            enlistment.Done();
        }
        #endregion

        #region accessors and modifiers
        private void Rollback(DataTable dtable)
        {
            if (!m_ChangedRows.ContainsKey(dtable)) 
                return;
            bool constraintsAreEnabled = m_Source.EnforceConstraints;
            m_Source.EnforceConstraints = false;
            foreach (DataRow dr in m_ChangedRows[dtable].Keys)
            {
                if (dr.RowState != DataRowState.Detached) 
                    dtable.Rows.Remove(dr);
            }

            foreach (DataRow dr in m_Clone.Tables[dtable.TableName].Rows)
            {
                dtable.ImportRow(dr);
            }

            m_ChangedRows.Remove(dtable);
            m_Source.EnforceConstraints = constraintsAreEnabled;
        }

        private void Delist()
        {
            m_Clone.Dispose();
            m_CurrentTransaction = null;
            m_Clone = null;

            foreach (DataTable dt in m_Source.Tables)
            {
                dt.ColumnChanging -= new DataColumnChangeEventHandler(DataColumnChangeEventHandler);
                dt.RowChanging -= new DataRowChangeEventHandler(DataRowChangeEventHandler);
                dt.RowDeleting -= new DataRowChangeEventHandler(DataRowChangeEventHandler);
                dt.TableClearing -= new DataTableClearEventHandler(DataTableClearEventHandler);
            }
        }

        public void Enlist()
        {
            if (null != m_CurrentTransaction) 
                throw new TransactionException();
            m_CurrentTransaction = Transaction.Current;
            if (m_CurrentTransaction.TransactionInformation.Status != TransactionStatus.Active) 
                throw new TransactionException();
            m_CurrentTransaction.EnlistVolatile(this, EnlistmentOptions.None);
            m_Clone = m_Source.Clone();
            m_Clone.EnforceConstraints = false;

            foreach (DataTable dt in m_Source.Tables)
            {
                dt.ColumnChanging += new DataColumnChangeEventHandler(DataColumnChangeEventHandler);
                dt.RowChanging += new DataRowChangeEventHandler(DataRowChangeEventHandler);
                dt.RowDeleting += new DataRowChangeEventHandler(DataRowChangeEventHandler);
                dt.TableClearing += new DataTableClearEventHandler(DataTableClearEventHandler);
            }
        }
        #endregion

        
    }
}